"""
Event dispatchers for the continual learning framework.

This module provides event dispatchers that route events to registered handlers.
Both synchronous and asynchronous dispatchers are available.
"""

import asyncio
import logging
import traceback
from collections import defaultdict
from typing import (
    Dict,
    List,
    Set,
    Type,
    TypeVar,
    Generic,
    Optional,
    Union,
    Any,
    Callable,
)

from .event_types import EventData, EventType, E
from .handlers import EventHandler, AsyncEventHandler, Handler


class EventDispatcher:
    """
    Dispatches events to registered handlers.

    This dispatcher processes events synchronously. For asynchronous processing,
    use AsyncEventDispatcher instead.
    """

    def __init__(self, name: str = "EventDispatcher"):
        """
        Initialize the event dispatcher.

        Args:
            name: Name for this dispatcher instance
        """
        self.name = name
        self._handlers: Dict[EventType, List[EventHandler]] = defaultdict(list)
        self._logger = logging.getLogger(f"{self.__class__.__name__}.{name}")

    def register_handler(self, handler: EventHandler) -> None:
        """
        Register an event handler.

        Args:
            handler: Handler to register
        """
        self._handlers[handler.event_type].append(handler)
        # Sort handlers by priority (higher priority first)
        self._handlers[handler.event_type].sort(reverse=True)
        self._logger.debug(
            f"Registered handler {handler.name} for event type {handler.event_type.name}"
        )

    def unregister_handler(self, handler: EventHandler) -> bool:
        """
        Unregister an event handler.

        Args:
            handler: Handler to unregister

        Returns:
            bool: True if the handler was removed, False if it wasn't registered
        """
        if (
            handler.event_type in self._handlers
            and handler in self._handlers[handler.event_type]
        ):
            self._handlers[handler.event_type].remove(handler)
            self._logger.debug(
                f"Unregistered handler {handler.name} for event type {handler.event_type.name}"
            )
            return True
        return False

    def dispatch(self, event: EventData) -> None:
        """
        Dispatch an event to registered handlers.

        Args:
            event: Event data to dispatch
        """
        if event.event_type not in self._handlers:
            self._logger.debug(
                f"No handlers registered for event type {event.event_type.name}"
            )
            return

        for handler in self._handlers[event.event_type]:
            try:
                handler(event)
            except Exception as e:
                self._logger.error(
                    f"Error in handler {handler.name} processing event {event.event_type.name}: {e}"
                )
                self._logger.debug(traceback.format_exc())

    def dispatch_batch(self, events: List[EventData]) -> None:
        """
        Dispatch multiple events.

        Args:
            events: List of events to dispatch
        """
        for event in events:
            self.dispatch(event)

    def has_handlers_for(self, event_type: EventType) -> bool:
        """
        Check if there are handlers registered for an event type.

        Args:
            event_type: Event type to check

        Returns:
            bool: True if there are handlers registered for the event type
        """
        return event_type in self._handlers and len(self._handlers[event_type]) > 0


class AsyncEventDispatcher:
    """
    Dispatches events asynchronously to registered handlers.

    This dispatcher can handle both synchronous and asynchronous handlers.
    Synchronous handlers will be wrapped to run in the event loop.
    """

    def __init__(self, name: str = "AsyncEventDispatcher"):
        """
        Initialize the async event dispatcher.

        Args:
            name: Name for this dispatcher instance
        """
        self.name = name
        self._sync_handlers: Dict[EventType, List[EventHandler]] = defaultdict(list)
        self._async_handlers: Dict[EventType, List[AsyncEventHandler]] = defaultdict(
            list
        )
        self._logger = logging.getLogger(f"{self.__class__.__name__}.{name}")

    def register_handler(self, handler: Union[EventHandler, AsyncEventHandler]) -> None:
        """
        Register an event handler.

        Args:
            handler: Handler to register (either synchronous or asynchronous)
        """
        if isinstance(handler, AsyncEventHandler):
            self._async_handlers[handler.event_type].append(handler)
            # Sort handlers by priority (higher priority first)
            self._async_handlers[handler.event_type].sort(reverse=True)
        else:
            self._sync_handlers[handler.event_type].append(handler)
            # Sort handlers by priority (higher priority first)
            self._sync_handlers[handler.event_type].sort(reverse=True)

        self._logger.debug(
            f"Registered {'async' if isinstance(handler, AsyncEventHandler) else 'sync'} "
            f"handler {handler.name} for event type {handler.event_type.name}"
        )

    def unregister_handler(
        self, handler: Union[EventHandler, AsyncEventHandler]
    ) -> bool:
        """
        Unregister an event handler.

        Args:
            handler: Handler to unregister

        Returns:
            bool: True if the handler was removed, False if it wasn't registered
        """
        if isinstance(handler, AsyncEventHandler):
            if (
                handler.event_type in self._async_handlers
                and handler in self._async_handlers[handler.event_type]
            ):
                self._async_handlers[handler.event_type].remove(handler)
                self._logger.debug(
                    f"Unregistered async handler {handler.name} for "
                    f"event type {handler.event_type.name}"
                )
                return True
        else:
            if (
                handler.event_type in self._sync_handlers
                and handler in self._sync_handlers[handler.event_type]
            ):
                self._sync_handlers[handler.event_type].remove(handler)
                self._logger.debug(
                    f"Unregistered sync handler {handler.name} for "
                    f"event type {handler.event_type.name}"
                )
                return True
        return False

    async def dispatch(self, event: EventData) -> None:
        """
        Dispatch an event to registered handlers asynchronously.

        Args:
            event: Event data to dispatch
        """
        sync_handlers = self._sync_handlers.get(event.event_type, [])
        async_handlers = self._async_handlers.get(event.event_type, [])

        if not sync_handlers and not async_handlers:
            self._logger.debug(
                f"No handlers registered for event type {event.event_type.name}"
            )
            return

        # Process synchronous handlers using asyncio.to_thread
        sync_tasks = []
        for handler in sync_handlers:
            try:
                # Execute synchronous handlers in thread pool
                task = asyncio.create_task(asyncio.to_thread(handler, event))
                sync_tasks.append(task)
            except Exception as e:
                self._logger.error(
                    f"Error scheduling sync handler {handler.name} for "
                    f"event {event.event_type.name}: {e}"
                )
                self._logger.debug(traceback.format_exc())

        # Process asynchronous handlers
        async_tasks = []
        for handler in async_handlers:
            try:
                # Execute asynchronous handlers directly
                task = asyncio.create_task(handler(event))
                async_tasks.append(task)
            except Exception as e:
                self._logger.error(
                    f"Error scheduling async handler {handler.name} for "
                    f"event {event.event_type.name}: {e}"
                )
                self._logger.debug(traceback.format_exc())

        # Wait for all handlers to complete
        if sync_tasks or async_tasks:
            await asyncio.gather(*sync_tasks, *async_tasks, return_exceptions=True)

    async def dispatch_batch(self, events: List[EventData]) -> None:
        """
        Dispatch multiple events asynchronously.

        Args:
            events: List of events to dispatch
        """
        tasks = []
        for event in events:
            tasks.append(self.dispatch(event))

        if tasks:
            await asyncio.gather(*tasks, return_exceptions=True)

    def has_handlers_for(self, event_type: EventType) -> bool:
        """
        Check if there are handlers registered for an event type.

        Args:
            event_type: Event type to check

        Returns:
            bool: True if there are handlers registered for the event type
        """
        has_sync = (
            event_type in self._sync_handlers
            and len(self._sync_handlers[event_type]) > 0
        )
        has_async = (
            event_type in self._async_handlers
            and len(self._async_handlers[event_type]) > 0
        )
        return has_sync or has_async


# Create a global dispatcher instance
global_dispatcher = EventDispatcher(name="GlobalDispatcher")
global_async_dispatcher = AsyncEventDispatcher(name="GlobalAsyncDispatcher")
